﻿/* 2012/4/15 */
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Collections.Specialized;
using Cosmos.IO;
using Cosmos.IO.FileSystemOperation;

namespace Cosmos.Windows.Forms
{
    /// <summary>
    /// Represents a tree view control, whose nodes represent folders.
    /// </summary>
    [DefaultProperty("SelectedPath")]
    [DefaultEvent("FolderSelected")]
    public partial class FolderTreeView : UserControl
    {
        private const string ToCheck = "ToCheck";
        private const string Checked = "Checked";
        private const string ToSelect = "ToSelect";
        private const string Selected = "Selected";

        /// <summary>
        /// Prepare to move a folder.
        /// </summary>
        private bool _moveFolder;
        /// <summary>
        /// Prepare to copy a folder.
        /// </summary>
        private bool _copyFolder;

        /// <summary>
        /// Initialize a instance of FolderTreeView.
        /// </summary>
        public FolderTreeView()
        {
            InitializeComponent();

            treeView.AllowDrop = true;

            //treeView.ImageList = folderImageList;
            //treeView.StateImageList = stateImageList;

            _moveFolder = false;
            _copyFolder = false;
        }

        /// <summary>
        /// Load settings of FolderTreeView.
        /// </summary>
        /// <param name="rootFolderPaths">Paths of root folders.</param>
        /// <param name="selectedFolderPath">FilePath of the selected folder.</param>
        public void LoadSettings(StringCollection rootFolderPaths, string selectedFolderPath)
        {
            StringBuilder inexistentPaths = new StringBuilder();
            treeView.Nodes.Clear();
            foreach (string path in rootFolderPaths)
            {
                if (Directory.Exists(path))
                    AddRootFolder(path);
                else
                    inexistentPaths.AppendLine(path);
            }
            SelectFolderTreeNode(selectedFolderPath, treeView.Nodes);
        }

        /// <summary>
        /// Save settings of FolderTreeView.
        /// </summary>
        /// <param name="rootFolderPaths">Paths of root folders.</param>
        /// <param name="selectedFolderPath">FilePath of the selected folder.</param>
        public void SaveSettings(StringCollection rootFolderPaths, out string selectedFolderPath)
        {
            rootFolderPaths.Clear();
            foreach (FolderTreeNode aNode in treeView.Nodes)
            {
                rootFolderPaths.Add(aNode.FolderPath);
            }
            if (treeView.SelectedNode != null)
                selectedFolderPath = ((FolderTreeNode)treeView.SelectedNode).FolderPath;
            else
                selectedFolderPath = null;
        }

        /// <summary>
        /// Reload selected folder in FolderTreeView, the sub-folders will be cleared and added again.
        /// </summary>
        public void ReloadSelectedFolder()
        {
            if (treeView.SelectedNode != null)
            {
                LoadFolderTreeSubnode((FolderTreeNode)treeView.SelectedNode);
            }
        }

        /// <summary>
        /// Open the path represented by the selected FolderTreeNode.
        /// </summary>
        public void OpenSelectedFolder()
        {
            if (treeView.SelectedNode != null)
                SystemCommand.OpenFolder(SelectedPath);
        }

        /// <summary>
        /// Add a root node to the FolderTreeView by giving the path of the node.
        /// </summary>
        /// <param name="path">The path represented by the node.</param>
        public void AddRootFolder(string path)
        {
            if (Directory.Exists(path))
            {
                DirectoryInfo directory = new DirectoryInfo(path);
                FolderTreeNode node = new FolderTreeNode(directory.Name, directory.FullName);

                treeView.Nodes.Add(node);
            }
        }

        /// <summary>
        /// Insert a node to the specified index in FolderTreeView with specified the path of the node.
        /// </summary>
        /// <param name="index">The index at which the node should be inserted.</param>
        /// <param name="path">The path of the node to insert.</param>
        public void InsertRootFolder(int index, string path)
        {
            if (Directory.Exists(path))
            {
                DirectoryInfo directory = new DirectoryInfo(path);
                FolderTreeNode node = new FolderTreeNode(directory.Name, directory.FullName);

                treeView.Nodes.Insert(index, node);
            }
        }

        /// <summary>
        /// Remove the selected root node from FolderTreeView.
        /// </summary>
        public void RemoveRootFolder()
        {
            TreeNode selectedNode = treeView.SelectedNode;

            //Just delete the root node.
            if (selectedNode != null && selectedNode.Parent == null)
                treeView.Nodes.Remove(selectedNode);
        }

        /// <summary>
        /// Remove a root node of FolderTreeView at the specified index.
        /// </summary>
        /// <param name="index">The index of the node to remove.</param>
        public void RemoveFolderAt(int index)
        {
            if (treeView.Nodes.Count > index)
                treeView.Nodes.RemoveAt(index);
        }

        /// <summary>
        /// SetPath selected folder in FolderTreeView.
        /// </summary>
        /// <param name="folderName">New name of folder.</param>
        /// <exception cref="InvalidOperationException">No folder is selected.</exception>
        /// <exception cref="IOException">Fail to rename the folder in file system.</exception>
        public void RenameSelectedFolder(string folderName)
        {
            if (treeView.SelectedNode != null)
            {
                string srcFolderPath = SelectedPath;
                string dstFolderPath = Path.Combine(Path.GetDirectoryName(srcFolderPath), folderName);
                if (WindowsFileSystem.Rename(srcFolderPath, dstFolderPath))
                {
                    FolderTreeNode theNode = (FolderTreeNode)treeView.SelectedNode;
                    theNode.Text = folderName;
                    theNode.FolderPath = dstFolderPath;
                }
                else
                {
                    throw new IOException("Fail to rename the folder.");
                }
            }
            else
            {
                throw new InvalidOperationException("No folder is selected.");
            }
        }

        /// <summary>
        /// Delete selected folder in FolderTreeView.
        /// </summary>
        /// <exception cref="InvalidOperationException">No folder is selected.</exception>
        /// <exception cref="IOException">Fail to delete the folder in file system.</exception>
        public void DeleteSelectedFolder()
        {
            if (treeView.SelectedNode != null)
            {
                if (WindowsFileSystem.Delete(SelectedPath))
                {
                    TreeNode parentNode = treeView.SelectedNode.Parent;
                    if (parentNode == null)
                    {
                        RemoveRootFolder();
                    }
                    else
                    {
                        treeView.SelectedNode = parentNode;
                    }
                }
                else
                {
                    throw new IOException("Fail to delete the folder.");
                }
            }
            else
            {
                throw new InvalidOperationException("No folder is selected.");
            }
        }

        /// <summary>
        /// Add path of selected folder to Clipboard, prepare to move this folder.
        /// </summary>
        public void CutSelectedFolder()
        {
            if (treeView.SelectedNode == null)
                return;

            AddFolderToClipboard();
            _moveFolder = true;
            _copyFolder = false;
        }

        /// <summary>
        /// Add path of selected folder to Clipboard, prepare to copy this folder.
        /// </summary>
        public void CopySelectedFolder()
        {
            if (treeView.SelectedNode == null)
                return;

            AddFolderToClipboard();
            _copyFolder = true;
            _moveFolder = false;
        }

        /// <summary>
        /// Paste folder which is moved or copied.
        /// </summary>
        /// <exception cref="IOException">Fail to move or copy the folder.</exception>
        public void PasteFolder()
        {
            StringCollection fileDropList = Clipboard.GetFileDropList();
            if (fileDropList == null || fileDropList.Count != 1)
                return;

            string folderPath = fileDropList[0]; 
            if (_moveFolder)
            {
                if (!WindowsFileSystem.Move(folderPath, SelectedPath))
                    throw new IOException("Fail to move the folder.");

                _moveFolder = false;
                LoadFolderTreeSubnode((FolderTreeNode)treeView.SelectedNode);
            }
            else if (_copyFolder)
            {
                if (!WindowsFileSystem.Copy(folderPath, SelectedPath))
                    throw new IOException("Fail to copy the folder.");

                _copyFolder = false;
                LoadFolderTreeSubnode((FolderTreeNode)treeView.SelectedNode);
            }
        }

        protected void OnFolderSelected(FolderPathEventArgs e)
        {
            if (FolderSelected != null)
                FolderSelected(this, e);
        }

        #region File System Methods
        
        /// <summary>
        /// Add path of selected folder to Clipboard.
        /// </summary>
        private void AddFolderToClipboard()
        {
            if (treeView.SelectedNode != null)
            {
                StringCollection fileDropList = new StringCollection();
                fileDropList.Add(SelectedPath);
                Clipboard.SetFileDropList(fileDropList);
            }
        }

        #endregion

        #region FolderTreeNode Methods

        /// <summary>
        /// Load sub-nodes of a FolderTreeNode.
        /// </summary>
        /// <param name="node">The node to load.</param>
        private void LoadFolderTreeSubnode(FolderTreeNode node)
        {
            if (node.Nodes.Count != 0)
                node.Nodes.Clear();

            DirectoryInfo directory = new DirectoryInfo(node.FolderPath);
            foreach (DirectoryInfo aDirectory in directory.GetDirectories())
            {
                FolderTreeNode aNode = new FolderTreeNode(aDirectory.Name, aDirectory.FullName);
                node.Nodes.Add(aNode);
            }
        }

        /// <summary>
        /// Expand FolderTreeNodes from root to a specified FolderTreeNode, and select the node.
        /// </summary>
        /// <param name="nodePath">The path indicated by the FolderTreeNode.</param>
        /// <param name="rootNodes">The root FolderTreeNodes.</param>
        private void SelectFolderTreeNode(string nodePath, TreeNodeCollection rootNodes)
        {
            if (!Directory.Exists(nodePath))
                return;

            Stack<string> directoryStack = new Stack<string>();
            string currentDirectory = nodePath;

            while (true)
            {
                bool found = false;
                foreach (FolderTreeNode aNode in rootNodes)
                {
                    if (aNode.FolderPath == currentDirectory)
                    {
                        directoryStack.Push(aNode.FolderPath);
                        found = true;
                    }
                }
                if (found)
                    break;
                else
                {
                    directoryStack.Push(currentDirectory);
                    DirectoryInfo nodeDirectory = new DirectoryInfo(currentDirectory);
                    currentDirectory = nodeDirectory.Parent.FullName;
                }
            }

            string resultPath = directoryStack.Peek();

            TreeNodeCollection nodeList = rootNodes;
            while (directoryStack.Count != 0)
            {
                currentDirectory = directoryStack.Pop();
                for (int i = 0; i < nodeList.Count; i++)
                {
                    FolderTreeNode currentNode = (FolderTreeNode)nodeList[i];

                    if (currentNode.FolderPath == currentDirectory)
                    {
                        if (directoryStack.Count == 0)
                        {
                            treeView.SelectedNode = currentNode;
                            OnFolderSelected(new FolderPathEventArgs(currentNode.FolderPath));
                        }
                        else
                        {
                            LoadFolderTreeSubnode(currentNode);
                            currentNode.Expand();
                            nodeList = currentNode.Nodes;
                            break;
                        }
                    }
                }
            }
        }

        #endregion

        #region Event Handler Methods

        private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            TreeView self = sender as TreeView;
            FolderTreeNode theNode = (FolderTreeNode)self.SelectedNode;
            while (!Directory.Exists(theNode.FolderPath))
            {
                if (theNode.Parent == null)
                {
                    RemoveRootFolder();
                    return;
                }
                else
                {
                    theNode = (FolderTreeNode)theNode.Parent;
                }
            }
            LoadFolderTreeSubnode(theNode);
            OnFolderSelected(new FolderPathEventArgs(theNode.FolderPath));
        }

        private void treeView_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                TreeView self = sender as TreeView;
                TreeNode clickedNode = self.GetNodeAt(e.Location);
                self.SelectedNode = clickedNode;
            }
        }

        private void treeView_ItemDrag(object sender, ItemDragEventArgs e)
        {
            DataObject theDataObject = new DataObject();
            theDataObject.SetData(typeof(FolderTreeNode), e.Item);
            theDataObject.SetData(DataFormats.StringFormat, ((FolderTreeNode)e.Item).FolderPath);
            TreeNode theNode = (TreeNode)e.Item;
            if (theNode.Parent == null)
            {
                DoDragDrop(theDataObject, DragDropEffects.Move);
            }
            else
            {
                DoDragDrop(theDataObject, DragDropEffects.Copy);
            }
        }

        private void treeView_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = e.AllowedEffect;
        }

        private void treeView_DragLeave(object sender, EventArgs e)
        {
            
        }

        private void treeView_DragOver(object sender, DragEventArgs e)
        {
            TreeView self = sender as TreeView;
            Point targetPoint = self.PointToClient(new Point(e.X, e.Y));
            TreeNode droppedNode = self.GetNodeAt(targetPoint);
            if (droppedNode != null && droppedNode.Parent != null)
                e.Effect = DragDropEffects.None;
            else if (e.Data.GetDataPresent(typeof(FolderTreeNode)))
                e.Effect = e.AllowedEffect;
            else if (e.Data.GetDataPresent(DataFormats.FileDrop) && (e.AllowedEffect & DragDropEffects.Link) == DragDropEffects.Link)
                e.Effect = DragDropEffects.Link;
            else
                e.Effect = DragDropEffects.None;
        }

        private void treeView_DragDrop(object sender, DragEventArgs e)
        {
            TreeView self = sender as TreeView;
            Point targetPoint = self.PointToClient(new Point(e.X, e.Y));
            TreeNode droppedNode = self.GetNodeAt(targetPoint);
            if (e.Data.GetDataPresent(typeof(FolderTreeNode)))
            {
                if ((e.AllowedEffect & DragDropEffects.Move) == DragDropEffects.Move)
                {
                    TreeNode draggedNode = (TreeNode)e.Data.GetData(typeof(FolderTreeNode));

                    if (droppedNode == null)
                    {
                        // Move the dragged node to the end.
                        draggedNode.Remove();
                        self.Nodes.Add(draggedNode);
                    }
                    else if (!draggedNode.Equals(droppedNode) && droppedNode.Parent == null)
                    {
                        // Move the dragged node the the place of dropped node.
                        int droppedIndex = self.Nodes.IndexOf(droppedNode);
                        int draggedIndex = self.Nodes.IndexOf(draggedNode);
                        self.Nodes.RemoveAt(draggedIndex);
                        if (droppedIndex > draggedIndex)
                            droppedIndex--;
                        self.Nodes.Insert(droppedIndex, draggedNode);
                    }

                    e.Effect = DragDropEffects.Move;
                }
                else if ((e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy)
                {
                    FolderTreeNode draggedNode = (FolderTreeNode)e.Data.GetData(typeof(FolderTreeNode));

                    if (droppedNode == null)
                        AddRootFolder(draggedNode.FolderPath);
                    else
                        InsertRootFolder(self.Nodes.IndexOf(droppedNode), draggedNode.FolderPath);

                    e.Effect = DragDropEffects.Copy;
                }

            }
            else if (e.Data.GetDataPresent(DataFormats.FileDrop) && (e.AllowedEffect & DragDropEffects.Link) == DragDropEffects.Link)
            {
                string[] paths = e.Data.GetData(DataFormats.FileDrop) as string[];
                if (paths != null)
                    foreach (string aPath in paths)
                        if (Directory.Exists(aPath))
                        {
                            if (droppedNode != null)
                                InsertRootFolder(self.Nodes.IndexOf(droppedNode), aPath);
                            else
                                AddRootFolder(aPath);
                        }
                e.Effect = DragDropEffects.Link;
            }
        }

        #endregion

        /// <summary>
        /// Gets the folder path represented by selected node.
        /// </summary>
        [Browsable(false)]
        public string SelectedPath
        {
            get 
            {
                if (treeView.SelectedNode != null)
                    return ((FolderTreeNode)treeView.SelectedNode).FolderPath;
                else
                    return null;
            }
        }

        /// <summary>
        /// Gets a value indicating whether a folder is cut or copied and it can be pasted.
        /// </summary>
        [Browsable(false)]
        public bool CanPaste
        {
            get { return _moveFolder || _copyFolder; }
        }

        /// <summary>
        /// Gets or sets the image for each node when it is in an unselected state.
        /// </summary>
        [Category("Folder")]
        [Description("The image for each node when it is in an unselected state.")]
        [DefaultValue(null)]
        public Image ToSelectImage
        {
            get
            {
                if (folderImageList.Images.ContainsKey(ToSelect))
                    return folderImageList.Images[ToSelect];
                else
                    return null;
            }
            set
            {
                if (value != null)
                {
                    if (treeView.ImageList == null)
                    {
                        treeView.ImageList = folderImageList;
                    }
                    folderImageList.Images.Add(ToSelect, value);
                    treeView.ImageKey = ToSelect;
                }
                else
                {
                    treeView.ImageKey = null;
                    folderImageList.Images.RemoveByKey(ToSelect);
                    if (treeView.SelectedImageKey == null)
                    {
                        treeView.ImageList = null;
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the image that is displayed when a node is selected.
        /// </summary>
        [Category("Folder")]
        [Description("The image that is displayed when a node is selected.")]
        [DefaultValue(null)]
        public Image SelectedImage
        {
            get
            {
                if (folderImageList.Images.ContainsKey(Selected))
                    return folderImageList.Images[Selected];
                else
                    return null;
            }
            set
            {
                if (value != null)
                {
                    if (treeView.ImageList == null)
                    {
                        treeView.ImageList = folderImageList;
                    }
                    folderImageList.Images.Add(Selected, value);
                    treeView.SelectedImageKey = Selected;
                }
                else
                {
                    treeView.SelectedImageKey = null;
                    folderImageList.Images.RemoveByKey(Selected);
                    if (treeView.ImageKey == null)
                    {
                        treeView.ImageList = null;
                    }
                }
            }
        }

        /// <summary>
        /// Occurs when a folder has been selected.
        /// </summary>
        [Category("Folder")]
        [Description("Occurs when a folder has been selected.")]
        public event FolderPathEventHandler FolderSelected;
    }
}


/*if (inexistentPaths.Length != 0)
                mainNotifyIcon.ShowBalloonTip(_BALLOON_TIMEOUT, "文件夹不存在", inexistentPaths.ToString(), ToolTipIcon.Error);*/



/*
        /// <summary>
        /// Add files of the folder denoted by the selected FolderTreeNode to file baseket.
        /// </summary>
        private void AddFolderTreeNodeToBasket()
        {
            FolderTreeNode selectedNode = (FolderTreeNode)treeView.SelectedNode;
            if (selectedNode != null && Directory.Exists(selectedNode.FolderPath))
            {
                DirectoryInfo directory = new DirectoryInfo(selectedNode.FolderPath);
                foreach (FileInfo aFile in directory.GetFiles())
                {
                    AddToFileBasket(aFile.Name, aFile.DirectoryName);
                }
            }
        }*/




/*ClearImage();
            LoadFileList(theNode.FolderPath);*/

            /*
            if (tabControl.SelectedTab != fileTabPage)
                tabControl.SelectedTab = fileTabPage;*/